package il.ac.technion.cs236700.utils;

import il.ac.technion.cs236700.utils.visitor.Action;
import il.ac.technion.cs236700.utils.visitor.SimpleVisitor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * Auxiliary general purpose (that is: non-domain specific) methods 
 */
public class Misc {
      
   private Misc() { 
      
   }
   
   /**
    * Compute a hash value for a set of objects.
    * Null values are considered to have hash value 0.
    * 
    * @param os Values 
    * @return hash value
    */
   public static int hash(Object... os) {
      int result = 0;
      for(Object o : os)
         result ^= (o == null ? 0 : o.hashCode());
      return result;
   }

   /**
    * Downcast an object if its class is the same as that of another object.
    * 
    * @param <T> Type to cast to
    * @param to An object providing the class to cast to 
    * @param from An object to downcast
    * @return <code>null</code> - if <code>from</code> is null or is not of the 
    * same class as <code>to</code>. Otherwise, return <code>from</code> casted 
    * to <code>T</code>.
    */
   @SuppressWarnings("unchecked")
   public static<T> T cast(T to, Object from) {
      if(to == from)
         return to;
      
      if(from == null)
         return null;
      
      Class<?> c = to.getClass();
      if(!c.equals(from.getClass()))
         return null;
      
      return (T) c.cast(from);
   }
   
   /**
    * Check the equality of two objects (including null values)
    * @param a An object
    * @param b An object
    * @return if a is null returns b == null, otherwise returns a.equals(b)
    */
   public static boolean eq(Object a, Object b) {
      return a == null ? b == null : a.equals(b);      
   }
   
   /**
    * A builder (with a fluent interface) for map objects. 
    *
    * @param <K> Type of keys
    * @param <V> Type of values
    */
   public static class MapBuilder<K,V> {
      /**
       * The final product
       */
      public final Map<K,V> map = new HashMap<K,V>();
      
      /**
       * Associate a value with a key
       * @param k Key 
       * @param v Value
       * @return this
       */
      public MapBuilder<K,V> map(K k, V v) {
         map.put(k, v);
         return this;
      }
   }
   
   /**
    * Obtain a new {@link MapBuilder} object, configured with the given 
    * key-value pair.
    * @param <K> Type of keys
    * @param <V> Type of values
    * @param k Key 
    * @param v Value
    * @return A new {@link MapBuilder} object with the pair <code>k</code>, 
    * <code>v</code>.
    */
   public static<K,V> MapBuilder<K, V> map(K k, V v) {
      return new MapBuilder<K, V>().map(k, v);
   }
   
   @SuppressWarnings("unchecked")
public static<K,V> Map<K,V> cloneMap(Map<K,V> map){
	   Map<K,V> copy = null;
	   try {
		copy = map.getClass().newInstance();
	} catch (InstantiationException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	} catch (IllegalAccessException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	copy.putAll(map);
	return copy;
   }

public static <T extends MyClonable<T>> ArrayList<T> cloneArray(ArrayList<T> orig) {
	final ArrayList<T> new_copy = new ArrayList<T>();
	new SimpleVisitor<T>(orig,new Action<T>(){
		@Override
		public void visit(T t) {
			new_copy.add(t.clone());			
		}
	}).go();
	return new_copy;	
}

public static List<Object> distinct(final Object[] array) {
	
	Arrays.sort(array);
	ArrayList<Object> $ = new ArrayList<Object>(Arrays.asList(array));
	for (int i=0;i<array.length-1;i++) {
		if (array[i].equals(array[i+1]))
			$.remove(array[i+1]);
	}
	return $;
}

}
 